import sys, os
import subprocess
import select
import datetime
import types
import re
# from threading import Thread
# import Queue

import shlex


COLOR_BLUE = "\033[94m"
COLOR_RED = "\033[91m"
COLOR_YELLOW = "\033[93m"
COLOR_GREEN = "\033[92m"
COLOR_MAGENTA = "\033[95m"
COLOR_CYAN = "\033[96m"
COLOR_RESET = "\033[0m"


class Log:
    def __init__(self):
        pass

    def LOG(self, level, string, TAG=""):        
        colors = {
                'V':COLOR_RESET,
                'D':COLOR_RESET,
                'I':COLOR_BLUE,
                'W':COLOR_YELLOW,
                'E':COLOR_RED,
                'T':COLOR_CYAN, # log for test and to be removed immediately after test
                'N':COLOR_MAGENTA, # log for dry-run which doesn't actually run cmd

                }

        if isinstance(string, list):
            string = "".join([x.encode('utf8') if isinstance(x, unicode) else str(x) for x in string])

        if level in colors:
            color = colors[level]
            self.__LOG(color, string, TAG)
        else: # if color None we should not add any color prefix
            tag = "" if not TAG else "[" + TAG + "] "
            print tag + string

    def __LOG(self, color, string, TAG=""):
        tag = "" if not TAG else "[" + TAG + "] "
        print color + tag + string + COLOR_RESET


__sLog = Log()

# some function need this to print "pure" string without any color prefix
# the string will be retured to some variable, if it contains color prefix, it's wrong
def LOG(string, TAG="", level=None):
    __sLog.LOG(level, string, TAG)

def LOGT(string, TAG=""):
    __sLog.LOG('T', string, TAG)

def LOGV(string, TAG=""):
    __sLog.LOG('V', string, TAG)

def LOGD(string, TAG=""):
    __sLog.LOG('D', string, TAG)

def LOGI(string, TAG=""):
    __sLog.LOG('I', string, TAG)

def LOGW(string, TAG=""):
    __sLog.LOG('W', string, TAG)

def LOGE(string, TAG=""):
    __sLog.LOG('E', string, TAG)

def LOGN(string, TAG=""):
    __sLog.LOG('N', string, TAG)

class ExecResult:
    def __init__(self, returncode=0, output=[], error=[]):
        self.returncode = returncode
        self.output = filter(bool,output) if output else None
        self.error = filter(bool,error) if error else None

    def hasError(self):
        return self.returncode != 0

    def hasOutput(self):
        return bool(self.output)

    def __str__(self):
        return "return code: " + str(self.returncode) + " output: " + str(self.output) + " error: " + str(self.error)

    def __repr__(self):
        return self.__str__()

def __exec_cmd_normal(cmdline, return_output, silence):
    # if out to pipe, use Popen.communicate
    # if out to std,  use Popen.wait
    # if out to pipe and Popen.wait, it may deadlock

    if not silence: LOGD("normal exec cmd: " + cmdline)

    code = 0;
    result = None
    try:
        # read stdout and stderr seperatly will deadlock after enough stderr produced
        # we may use two thread to read, or just redirect stderr to stdout
        stderr_to = subprocess.PIPE if return_output else subprocess.STDOUT
        if type(return_output) is (int or file):  # return_output is an fd or file
            stdout_to = return_output
        else:  # redirect out to PIPE if output request otherwise to stdout
            stdout_to = subprocess.PIPE if return_output else open("/dev/null", "w")

        child = subprocess.Popen(shlex.split(cmdline), stdout=stdout_to, stderr=stderr_to)

        if type(return_output) is not int:
            if return_output:
                result = child.communicate()  # wait for process exit
                if not silence:
                    for info in result[0].split("\n"):
                        if info: LOGV(info)
                    for info in result[1].split("\n"):
                        if info: LOGE(info)
            else:
                if silence:  # No result and no output, just wait for it
                    child.wait()
                else:  # if long-term running process, keep displaying its output
                    child.wait()

                    '''
                    while True:
                        reads = [child.stdout.fileno()]
                        ret = select.select(reads, [], [])
                        for fd in ret[0]:
                            if fd == child.stdout.fileno():
                                string = child.stdout.readline()
                                LOGV(string)

                        if child.poll() != None:
                            break
                    '''

                    '''
                    q1 = Queue()
                    q2 = Queue
                    Thread(target=reader, args=[child.stdout,q1]).start()
                    Thread(target=reader, args=[child.stderr,q2]).start()
                    for source, line in iter(q1.get, None):print source, line
                    for source, line in iter(q2.get, None):print source, line
                    '''

        else:  # TODO silence or not
            child.wait()

        code = child.returncode
    except Exception as e:
        LOGE("Exception: " + str(e))
        return ExecResult(-5, [], [str(e)])

    output = None
    error = None

    if result:
        if result[0] and len(result[0]) > 0:
            output = result[0].split("\n")
        if result[1] and len(result[1]) > 0:
            output = result[1].split("\n")

    ret = ExecResult(code, output, error)

    if not silence: LOGD("exec cmd " + "return " + str(code))

    return ret


# TODO get_output should be used for last cmd
def __exec_cmd_pipe(cmdline, return_output, silence):
    if not silence: LOGD("pipe exec cmd: " + cmdline)

    l = cmdline.split("|")
    subs = []
    code = 0;
    try:
        for cmd in l:
            if len(subs) < 1: # first cmd, out to pipe
                child = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            else:
                if cmd == l[-1]: # last cmd, out to requested file
                    if type(return_output) is (int or file):
                        stdout_to = return_output 
                    else:
                        stdout_to = subprocess.PIPE if bool(return_output) else open("/dev/null", "w")
                else: # middle cmd, input from last process's output and out to pipe
                    stdout_to = subprocess.PIPE

                child = subprocess.Popen(shlex.split(cmd), stdin=subs[-1].stdout, stdout=stdout_to, stderr=stdout_to)
            subs.append(child)

        if subs:
            result = subs[-1].communicate()
            code = subs[-1].returncode
            ret = ExecResult(code, result[0].split("\n") if result and result[0] else [""],
                             result[1].split("\n") if result and result[1] else [""])
        else:
            LOGW("cmd not been executed!")
            ret = ExecResult(-1, [], ["cmd not been executed!"])


    except Exception as e:
        LOGE("Exception: " + str(e))
        return ExecResult(-5, [], [str(e)])

    if not silence or code != 0:
        for info in ret.output:
            if info:
                LOGV(info)
        for info in ret.error:
            if info:
                LOGE(info)

    if not silence: LOGD("exec cmd " + "return " + str(code))

    return ret


def exec_cmd_under_dir(dir, cmdline, dry=False, return_output=False, silence=True):
    if not dry:
        prev = os.getcwd()
        try:
            if dir:
                os.chdir(dir)
                Utils.LOGT("chdir to " + dir)
            Utils.LOGT("cwd is " + os.getcwd())
            ret = exec_cmd(cmdline, dry, return_output, silence)
        finally:
            os.chdir(prev)
    else:
        LOGN("Under dir: " + str(dir) + " exec: " + cmdline)
        ret = ExecResult()

    return ret


# return_output: 
    # True: to return value
    # False: to /dev/null
    # fd or file: redirect output to file
# silence: if True, display the output anyway when output is not to the stdout
def exec_cmd(cmdline, dry=False, return_output=False, silence=True):
    if not cmdline:
        return ExecResult()

    if not dry:

        if ";" in cmdline and "&&" in cmdline:
            LOGE("TODO. For now we can only use either ';' or '&&'")
            return ExecResult(-1)
        sep = "&&" if "&&" in cmdline else ";"
        # for cmd sequence which seperated by ;
        sequence = split_not_in_quotes(cmdline, sep)

        for seq in sequence:
            if '|' not in seq:  # normal
                ret = __exec_cmd_normal(seq, return_output, silence)
                if sep == '&&' and ret.returncode != 0:
                    return ret
            else:  # handle pipe
                ret = __exec_cmd_pipe(seq, return_output, silence)  # TODO

    else:
        LOGN(cmdline)
        ret = ExecResult(0, ["dry-run"])

    return ret


def split_not_in_quotes(string, sep=";"):
    # quotes itself is not included in the result
    # reg = r'''[^;]*".*"[^;]*|[^;]*'.*'[^;]*|[^;"']+'''
    reg = r'''[^<SEP>]*".*"[^<SEP>]*|[^<SEP>]*'.*'[^<SEP>]*|[^<SEP>"']+'''.replace("<SEP>", sep)
    l = re.findall(reg, string)
    return l

def hasFunction(obj,string):
    if hasattr(obj, string):
        func = getattr(obj, string)
        return func if type(func) is types.FunctionType else None
    else:
        return None
def isMac():
    return sys.platform == 'darwin'

def installSubparser(parser, obj, cmds):
    subparsers = parser.add_subparsers(dest='subcmd')

    for (key, method) in cmds.iteritems():
        subparser = subparsers.add_parser(key, help = key + " help")
        subparser.set_defaults(func = method)
        # Add common args for each subcmd, not to use parent
        subparser.add_argument("--dry", "--dry-run", "-n", action="store_true", help="Not actually execute")
        subparser.add_argument("-v", action="count", help="Verbose")

        # Add function special arg if needed
        funcname = method.__name__ + "_args"
        arg_func =  hasFunction(obj, funcname)
        if arg_func:
            arg_func(subparser)

    return
